using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;

namespace S
{
    /// <summary>
    /// base class for entity that implements both idisposable and icloneable
    /// </summary>
    public abstract class Entity : IDisposable, ICloneable
    {
        /// <summary>
        /// entity constructor
        /// </summary>
        public Entity(Point pPosition, Size pSize)
        {
            // increment the autoid
            sAutoID++;

            // set the id
            this.mID = sAutoID;

            // set the position
            this.mBoundingBox.X = pPosition.X;
            this.mBoundingBox.Y = pPosition.Y;

            // set the size
            this.mBoundingBox.Width = pSize.Width;
            this.mBoundingBox.Height = pSize.Height;
        }

        /// <summary>
        /// get the entity ID
        /// </summary>
        public int ID
        {
            get
            {
                return this.mID;
            }
        }

        /// <summary>
        /// get or set the position of the entity within the screen
        /// </summary>
        public Point Position
        {
            get
            {
                return this.mBoundingBox.Location;
            }

            set
            {
                // set the position
                this.OnMove(value.X - this.mBoundingBox.X, value.Y - this.mBoundingBox.Y);

                // fire the moved event
                if (this.Moved != null)
                {
                    this.Moved.Invoke(this, EventArgs.Empty);
                }
            }
        }

        /// <summary>
        /// the size of the entity
        /// </summary>
        public Size Size
        {
            get
            {
                return this.mBoundingBox.Size;
            }

            set
            {
                // set the size
                this.OnScale(new Rectangle(0, 0, value.Width, value.Height));

                // fire the scale event
                if (this.Scaled != null)
                {
                    this.Scaled.Invoke(this, EventArgs.Empty);
                }
            }
        }

        /// <summary>
        /// readonly gets the bounding box of this entity in pixels
        /// </summary>
        public Rectangle BoundingBox
        {
            get
            {
                return this.mBoundingBox;
            }
        }

        /// <summary>
        /// get or set the number of screens refering to this entity
        /// </summary>
        public int RefCount
        {
            get
            {
                return this.mRefCount;
            }
            set
            {
                this.mRefCount = value;
            }
        }

        /// <summary>
        /// get or set whether the entity is locked or not
        /// </summary>
        public bool Locked
        {
            get
            {
                return this.mLocked;
            }
            set
            {
                this.mLocked = value;
            }
        }

        /// <summary>
        /// computes the bounding box for a bunch of entities
        /// </summary>
        /// <param name="pEntities">the entities to compute a bounding box for</param>
        /// <returns>a bounding box around all of the entities in the list</returns>
        public static Rectangle ComputeBoundingBox(S.Entity[] pEntities)
        {
            // if the list of entities is empty
            if (pEntities.Length == 0)
            {
                // then use the empty rectangle
                return Rectangle.Empty;
            }

            // use the first item as the base bounding box
            Rectangle boundingBox = pEntities[0].BoundingBox;

            // now keep adding the bounding boxes for all the entities
            for (int i = 1; i < pEntities.Length; i++)
            {
                boundingBox = Rectangle.Union(boundingBox, pEntities[i].BoundingBox);
            }

            // the total bounding box is the union of all the bounding boxes
            return boundingBox;
        }

        /// <summary>
        /// figure out whether or not entity lies within
        /// given pair of points 
        /// </summary>
        /// <param name="pX">x position to check</param>
        /// <param name="pY">y position to check</param>
        /// <returns>true if entity lies within pX, pY</returns>
        public abstract bool HitTest(int pX, int pY);

        /// <summary>
        /// tells entity to draw itself using the graphics object
        /// </summary>
        /// <param name="pGraphics">the graphics object to use</param>
        public abstract void Draw(Graphics pGraphics);

        /// <summary>
        /// move entity to new location
        /// </summary>
        /// <param name="pLocation">the new location to move entity to</param>
        public void Move(Point pLocation)
        {
            // move the entity
            this.OnMove(pLocation.X - this.mBoundingBox.X, pLocation.Y - this.mBoundingBox.Y);

            // fire the on move event
            if (this.Moved != null)
            {
                this.Moved.Invoke(this, EventArgs.Empty);
            }
        }

        /// <summary>
        /// move entity to x and y coordinates
        /// </summary>
        /// <param name="pX">the absolute x position</param>
        /// <param name="pY">the absolute y position</param>
        public void Move(int pX, int pY)
        {
            // move the entity
            this.OnMove(pX - this.mBoundingBox.X, pY - this.mBoundingBox.Y);

            // fire the on move event
            if (this.Moved != null)
            {
                this.Moved.Invoke(this, EventArgs.Empty);
            }
        }

        /// <summary>
        /// translate the entity by x and y delta values
        /// </summary>
        /// <param name="pDeltaX">the relative x amount to move</param>
        /// <param name="pDeltaY">the relative y amount to move</param>
        public void Translate(int pDeltaX, int pDeltaY)
        {
            // move the entity
            this.OnMove(pDeltaX, pDeltaY);

            // fire the moved event
            if (this.Moved != null)
            {
                this.Moved.Invoke(this, EventArgs.Empty);
            }
        }

        /// <summary>
        /// scale the entity to the new width and height
        /// </summary>
        /// <param name="pWidth">the new width to scale to</param>
        /// <param name="pHeight">the new height to scale to</param>
        public void Scale(int pWidth, int pHeight)
        {
            // scale the entity
            this.OnScale(new Rectangle(0, 0, pWidth, pHeight));

            // fire the scaled event
            if (this.Scaled != null)
            {
                this.Scaled.Invoke(this, EventArgs.Empty);
            }
        }

        /// <summary>
        /// scale the entity to the given rectangle
        /// </summary>
        /// <param name="pRectangle">the rectangle to scale to</param>
        public void ScaleToRectangle(Rectangle pRectangle)
        {
            // scale the entity
            this.OnScale(pRectangle);

            // fire the scaled event
            if (this.Scaled != null)
            {
                this.Scaled.Invoke(this, EventArgs.Empty);
            }
        }

        /// <summary>
        /// select the entity and fire the selected event
        /// </summary>
        public void Select()
        {
            // call the onselect
            this.OnSelect();

            // fire the selected event
            if (this.Selected != null)
            {
                this.Selected.Invoke(this, EventArgs.Empty);
            }
        }

        /// <summary>
        /// deselect the entity and fire the deselect event
        /// </summary>
        public void Deselect()
        {
            // call the ondeselect
            this.OnDeselect();

            // fire the deselected event
            if (this.Deselected != null)
            {
                this.Deselected.Invoke(this, EventArgs.Empty);
            }
        }

        /// <summary>
        /// dispose this entity
        /// </summary>
        public abstract void Dispose();

        /// <summary>
        /// clone this entity
        /// </summary>
        public abstract object Clone();

        /// <summary>
        /// the actual selection of the entity happpens here
        /// </summary>
        protected abstract void OnSelect();

        /// <summary>
        /// the actual deselection of the entity happens here
        /// </summary>
        protected abstract void OnDeselect();

        /// <summary>
        /// the actual moving of the entity happens here
        /// </summary>
        /// <param name="pDeltaX">the relative x amount to move entity by</param>
        /// <param name="pDeltaY">the relative y amount to move entity by</param>
        protected abstract void OnMove(int pDeltaX, int pDeltaY);

        /// <summary>
        /// the actual scaling of the entity happens here
        /// </summary>
        /// <param name="pScaleRectangle">the rectangle to scale to</param>
        protected abstract void OnScale(Rectangle pScaleRectangle);

        /// <summary>
        /// fires when a selection occurs on this entity
        /// </summary>
        public event EventHandler Selected;

        /// <summary>
        /// fires when a deselection occurs on this entity
        /// </summary>
        public event EventHandler Deselected;

        /// <summary>
        /// fires when the entity is moved
        /// </summary>
        public event EventHandler Moved;

        /// <summary>
        /// fires when the entity is scaled
        /// </summary>
        public event EventHandler Scaled;

        /// <summary>
        /// autoid for entities
        /// </summary>
        private static int sAutoID = 0;

        /// <summary>
        /// the id of the entity
        /// </summary>
        private int mID;

        /// <summary>
        /// the bouding box of the entity which contains a size and position
        /// </summary>
        protected Rectangle mBoundingBox;

        /// <summary>
        /// number of screen refering this entity
        /// </summary>
        private int mRefCount;

        /// <summary>
        /// whether or not this entity is locked
        /// </summary>
        private bool mLocked;
    }
}
